using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using static LightCAD.Three.Constants;
//import * as AnimationUtils from "./AnimationUtils.js";
namespace LightCAD.Three
{
    public class AnimationClip
    {
        #region scope properties or methods

        //private static string getTrackTypeForValueTypeName(string typeName, out Func<JObject, KeyframeTrack> parser)
        //{
        //    parser = null;
        //    switch (typeName.toLowerCase())
        //    {

        //        case "scalar":
        //        case "double":
        //        case "float":
        //        case "number":
        //        case "integer":
        //            parser = NumberKeyframeTrack.parser;
        //            return "NumberKeyframeTrack";

        //        case "vector":
        //        case "vector2":
        //        case "vector3":
        //        case "vector4":
        //            parser = VectorKeyframeTrack.parser;
        //            return "VectorKeyframeTrack";

        //        case "color":
        //            parser = ColorKeyframeTrack.parser;
        //            return "ColorKeyframeTrack";

        //        case "quaternion":
        //            parser = QuaternionKeyframeTrack.parser;
        //            return "QuaternionKeyframeTrack";

        //        case "bool":
        //        case "boolean":
        //            parser = BooleanKeyframeTrack.parser;
        //            return "BooleanKeyframeTrack";

        //        case "string":
        //            parser = StringKeyframeTrack.parser;
        //            return "StringKeyframeTrack";

        //    }

        //    throw new Error("THREE.KeyframeTrack: Unsupported typeName: " + typeName);

        //}
        //private static KeyframeTrack parseKeyframeTrack(JObject json)
        //{

        //    if (!json.ContainsKey("type"))
        //    {
        //        throw new Error("THREE.KeyframeTrack: track type undefined, can not parse");
        //    }
        //    var type = (string)json.GetValue("type");


        //    if (!json.ContainsKey("times"))
        //    {
        //        var times = new JsArr<double>();
        //        var values = new JsArr<double>();

        //        AnimationUtils.flattenJSON((JArray)json["keys"], times, values, "value");
        //        var jarr = new JArray();
        //        times.forEach(e => jarr.Add(e));
        //        json.Add("times", jarr);
        //        jarr = new JArray();
        //        values.forEach(e => jarr.Add(e));
        //        json.Add("values", jarr);

        //    }

        //    var typeName = getTrackTypeForValueTypeName(type, out Func<JObject, KeyframeTrack> trackParse);
        //    // derived classes can define a static parse method
        //    if (trackParse != null)
        //    {

        //        return trackParse(json);

        //    }
        //    else
        //    {
        //        var name = (string)json["name"];
        //        var jarr = (JArray)json["times"];
        //        var times = new JsArr<double>();
        //        foreach (var item in jarr) times.push((double)item);
        //        jarr = (JArray)json["times"];
        //        var values = new JsArr<double>();
        //        foreach (var item in jarr) values.push((double)item);

        //        var interpolation = (json["interpolation"].Type == JTokenType.Undefined) ? 0 : (int)json["interpolation"];

        //        KeyframeTrack track = AnimationUtils.CreateKeyframeTrack(typeName, name, times.ToArray(), values.ToArray(), interpolation);
        //        // by default, we assume a constructor compatible with the base
        //        return track;

        //    }

        //}
        #endregion

        #region Properties

        public string name;
        public JsArr<KeyframeTrack> tracks;
        public double duration;
        public int blendMode;
        public string uuid;
        public int fps;
        #endregion

        #region constructor
        public AnimationClip(string name, double? duration = null, JsArr<KeyframeTrack> tracks = null, int blendMode = NormalAnimationBlendMode)
        {
            this.name = name;
            this.tracks = tracks;
            this.duration = duration ?? -1;
            this.blendMode = blendMode;
            this.uuid = MathUtils.generateUUID();
            // this means it should figure out its duration by scanning the tracks
            if (this.duration < 0)
            {
                this.resetDuration();
            }
        }
        #endregion

        #region methods
        //public static AnimationClip parse(JObject json)
        //{
        //    var tracks = new JsArr<KeyframeTrack>();
        //    var jsonTracks = (JArray)json["tracks"];
        //    var fps = json["fps"].Type == JTokenType.Undefined ? 1.0 : (double)json["fps"];
        //    var frameTime = 1.0 / fps;
        //    for (int i = 0, n = jsonTracks.Count; i != n; ++i)
        //    {
        //        tracks.push(parseKeyframeTrack((JObject)jsonTracks[i]).scale(frameTime));
        //    }
        //    var name = (string)json["name"];
        //    var duration = (double)json["duration"];
        //    var blendMode = (int)json["blendMode"];

        //    var clip = new AnimationClip(name, duration, tracks, blendMode);
        //    clip.uuid = json["uuid"].ToString();
        //    return clip;
        //}
        //public static JObject toJSON(AnimationClip clip)
        //{
        //    var tracks = new JArray();
        //    var clipTracks = clip.tracks;
        //    var json = new JObject();
        //    json.Add("name", clip.name);
        //    json.Add("duration", clip.duration);
        //    json.Add("tracks", tracks);
        //    json.Add("uuid", clip.uuid);
        //    json.Add("blendMode", clip.blendMode);

        //    for (int i = 0, n = clipTracks.length; i != n; ++i)
        //    {
        //        tracks.Add(KeyframeTrack.toJSON(clipTracks[i]));
        //    }
        //    return json;
        //}
        public static AnimationClip CreateFromMorphTargetSequence(string name, JsArr<MorphTargetSequence> morphTargetSequence, double fps, object noLoop = null)
        {
            var numMorphTargets = morphTargetSequence.length;
            var tracks = new JsArr<KeyframeTrack>();
            for (int i = 0; i < numMorphTargets; i++)
            {
                var times = new JsArr<double>();
                var values = new JsArr<double>();
                times.push(
                    (i + numMorphTargets - 1) % numMorphTargets,
                    i,
                    (i + 1) % numMorphTargets);
                values.push(0, 1, 0);
                var order = AnimationUtils.getKeyframeOrder(times);
                times = AnimationUtils.sortedArray(times, 1, order);
                values = AnimationUtils.sortedArray(values, 1, order);
                // if there is a key at the first frame, duplicate it as the
                // last frame as well for perfect loop.
                if (noLoop == null && times[0] == 0)
                {
                    times.push(numMorphTargets);
                    values.push(values[0]);
                }
                tracks.push(
                    new NumberKeyframeTrack(
                        ".morphTargetInfluences[" + morphTargetSequence[i].name + "]",
                        times.ToArray(), values.ToArray()
                    ).scale(1.0 / fps));
            }
            return new AnimationClip(name, -1, tracks);
        }
        public static AnimationClip findByName(object objectOrClipArray, string name)
        {
            JsArr<AnimationClip> clipArray = null;
            if (objectOrClipArray is AnimationClip)
            {
                if (objectOrClipArray is IGeometry)
                {
                    var geo = (objectOrClipArray as IGeometry).getGeometry();
                    clipArray = (JsArr<AnimationClip>)geo.GetField("animations");
                }
                else if (objectOrClipArray is Object3D)
                {
                    var obj = objectOrClipArray as Object3D;
                    clipArray = obj.animations;
                }
            }
            for (int i = 0; i < clipArray.length; i++)
            {
                if (clipArray[i].name == name)
                {
                    return clipArray[i];
                }
            }
            return null;
        }
        public static object CreateClipsFromMorphTargetSequences(JsArr<MorphTargetSequence> morphTargets, double fps, object noLoop = null)
        {
            var animationToMorphTargets = new JsObj<JsArr<MorphTargetSequence>>();
            //// tested with https://regex101.com/ on trick sequences
            //// such flamingo_flyA_003, flamingo_run1_003, crdeath0059
            var pattern = new Regex(@"^([\w-]*?)([\d]+)$");
            //// sort morph target names into animation groups based
            //// patterns like Walk_001, Walk_002, Run_001, Run_002
            for (int i = 0, il = morphTargets.length; i < il; i++)
            {
                var morphTarget = morphTargets[i];
                var parts = pattern.Matches(morphTarget.name);
                if (parts.Count > 1)
                {
                    var name = parts[1].Value;
                    if (!animationToMorphTargets.ContainsKey(name))
                    {
                        animationToMorphTargets[name] = new JsArr<MorphTargetSequence>();
                    }
                    animationToMorphTargets[name].push(morphTarget);
                }
            }
            var clips = new JsArr<object>();
            foreach (var kvp in animationToMorphTargets)
            {
                var name = kvp.Key;
                var mtsArr = kvp.Value;
                clips.push(CreateFromMorphTargetSequence(name, mtsArr, fps, noLoop));
            }
            return clips;
        }
        //public static object parseAnimation(JObject animation, JArray bones)
        //{
        //    if (animation == null)
        //    {
        //        console.error("THREE.AnimationClip: No animation in JSONLoader data.");
        //        return null;
        //    }
        //    void addNonemptyTrack(string trackType, string trackName, JArray animationKeys, string propertyName, JsArr<KeyframeTrack> destTracks)
        //    {
        //        // only return track if there are actually keys.
        //        if (animationKeys.Count != 0)
        //        {
        //            var times = new JsArr<double>();
        //            var values = new JsArr<double>();
        //            AnimationUtils.flattenJSON(animationKeys, times, values, propertyName);
        //            // empty keys are filtered out, so check again
        //            if (times.length != 0)
        //            {
        //                destTracks.push(AnimationUtils.CreateKeyframeTrack(trackType, trackName, times.ToArray(), values.ToArray()));
        //            }
        //        }
        //    };

        //    var tracks = new JsArr<KeyframeTrack>();
        //    var clipName = animation["name"].Type == JTokenType.Undefined ? "default" : (string)animation["name"];
        //    var fps = animation["fps"].Type == JTokenType.Undefined ? 30 : (int)animation["fps"];
        //    var blendMode = (int)animation["blendMode"];
        //    // automatic length determination in AnimationClip.
        //    var duration = animation["length"].Type == JTokenType.Undefined ? -1 : (double)animation["length"];
        //    JArray hierarchyTracks = null;
        //    if (animation["hierarchy"].Type == JTokenType.Undefined)
        //        hierarchyTracks = new JArray();
        //    else
        //        hierarchyTracks = (JArray)animation["hierarchy"];

        //    for (int h = 0; h < hierarchyTracks.Count; h++)
        //    {
        //        var animationKeys = (JArray)hierarchyTracks[h]["keys"];
        //        // skip empty tracks
        //        if (animationKeys.Type == JTokenType.Undefined || animationKeys.Count == 0) continue;
        //        // process morph targets
        //        if (animationKeys[0]["morphTargets"].Type != JTokenType.Undefined)
        //        {
        //            // figure out all morph targets used in this track
        //            var morphTargetNames = new JsObj<int>();
        //            for (int k = 0; k < animationKeys.Count; k++)
        //            {
        //                var mtArr = (JArray)animationKeys[k]["morphTargets"];
        //                if (mtArr.Type != JTokenType.Undefined)
        //                {
        //                    for (int m = 0; m < mtArr.Count; m++)
        //                    {
        //                        morphTargetNames[mtArr[m].ToString()] = -1;
        //                    }
        //                }
        //                // create a track for each morph target with all zero
        //                // morphTargetInfluences except for the keys in which
        //                // the morphTarget is named.
        //                foreach (var item in morphTargetNames)
        //                {
        //                    var morphTargetName = item.Key;
        //                    var times = new JsArr<double>();
        //                    var values = new JsArr<double>();
        //                    for (int m = 0; m != mtArr.Count; ++m)
        //                    {
        //                        var animationKey = animationKeys[k];
        //                        times.push((double)animationKey["time"]);
        //                        values.push(((string)animationKey["morphTarget"] == morphTargetName) ? 1 : 0);
        //                    }
        //                    tracks.push(new NumberKeyframeTrack(".morphTargetInfluence[" + morphTargetName + "]", times.ToArray(), values.ToArray()));
        //                }
        //                duration = morphTargetNames.Count * fps;
        //            }
        //        }
        //        else
        //        {
        //            // ...assume skeletal animation
        //            var boneName = ".bones[" + bones[h]["name"] + "]";
        //            addNonemptyTrack(
        //                "VectorKeyframeTrack", boneName + ".position",
        //                animationKeys, "pos", tracks);
        //            addNonemptyTrack(
        //                "QuaternionKeyframeTrack", boneName + ".quaternion",
        //                animationKeys, "rot", tracks);
        //            addNonemptyTrack(
        //                "VectorKeyframeTrack", boneName + ".scale",
        //                animationKeys, "scl", tracks);
        //        }
        //    }
        //    if (tracks.length == 0)
        //    {
        //        return null;
        //    }
        //    var clip = new AnimationClip(clipName, duration, tracks, blendMode);
        //    return clip;
        //}
        public AnimationClip resetDuration()
        {
            var tracks = this.tracks;
            double duration = 0;
            for (int i = 0, n = tracks.length; i != n; ++i)
            {
                var track = this.tracks[i];
                duration = JMath.max(duration, track.times[track.times.Length - 1]);
            }
            this.duration = duration;
            return this;
        }
        public AnimationClip trim()
        {
            for (int i = 0; i < this.tracks.length; i++)
            {
                this.tracks[i].trim(0, this.duration);
            }
            return this;
        }
        public bool validate()
        {
            var valid = true;
            for (int i = 0; i < this.tracks.length; i++)
            {
                valid = valid && this.tracks[i].validate();
            }
            return valid;
        }
        public AnimationClip optimize()
        {
            for (int i = 0; i < this.tracks.length; i++)
            {
                this.tracks[i].optimize();
            }
            return this;
        }
        public AnimationClip clone()
        {
            var tracks = new JsArr<KeyframeTrack>();
            for (int i = 0; i < this.tracks.length; i++)
            {
                tracks.push(this.tracks[i].clone());
            }
            return new AnimationClip(this.name, this.duration, tracks, this.blendMode);
        }
        //public JObject toJSON()
        //{
        //    return AnimationClip.toJSON(this);
        //}
        #endregion
    }
}
